#pragma once
#include <stddef.h>
#include <string.h>

template <class T>

class CLoopBuffer
{
public:
    CLoopBuffer(void);
    ~CLoopBuffer(void);
    bool Init(int count, T* data = NULL);
    bool PushBack(const T* data, int count);
    bool PushBackEx(const T* data, int count, int reserved);
    bool PopFront(T* data, int count);
    bool GetFront(T* data, int count);
    void DiscardFront(int count);
    T* GetData(void);
    int GetCount(void);
    int GetFreeCount(void);
protected:
    char*   data;
    char*   head;
    char*   tail;
    int     size;
    bool    self_alloc;
};

template <class T>
CLoopBuffer<T>::CLoopBuffer(void)
{
    data        = NULL;
    head        = NULL;
    tail        = NULL;
    size        = 0;
    self_alloc  = false;
}

template <class T>
CLoopBuffer<T>::~CLoopBuffer(void)
{
    if (self_alloc)
    {
        delete[] data;
        data = NULL;
    }
}

template <class T>
bool CLoopBuffer<T>::Init(int count, T* data_ /* = NULL */)
{
    if (!data_ && !count)
    {
        data = NULL;
        size = 0;
        self_alloc = false;
    }
    else if (!data_)
    {
        data = new char[count*sizeof(T)];
        self_alloc = true;
    }
    else
    {
        data = (char*)data_;
        self_alloc = false;
    }

    if (!data)
    {
        return false;
    }

    head = data;
    tail = data;
    size = count*sizeof(T);

    return true;
}

template <class T>
bool CLoopBuffer<T>::PushBack(const T* data_, int count)
{
    count = count*sizeof(T);
    int dist = (int)(tail + size - head);
    int used = dist >= size ? (dist - size) : dist;
    if (count + used + 1 > size)
        return false;

    if (tail + count >= data + size)
    {
        int seg1 = (int)(data + size - tail);
        int seg2 = count - seg1;
        memcpy(tail, data_, seg1);
        memcpy(data, (char*)data_+seg1, seg2);
        tail = data + seg2;
    }
    else
    {
        memcpy(tail, data_, count);
        tail += count;
    }

    return true;
}

template <class T>
bool CLoopBuffer<T>::PushBackEx(const T* data_, int count, int reserved)
{
    count = count*sizeof(T);
    reserved = reserved*sizeof(T);

    int dist = (int)(tail + size - head);
    int used = dist >= size ? (dist - size) : dist;
    if (count + reserved + used + 1 > size)
        return false;

    if (tail + count >= data + size)
    {
        int seg1 = (int)(data + size - tail);
        int seg2 = count - seg1;
        memcpy(tail, data_, seg1);
        memcpy(data, (char*)data_+seg1, seg2);
        tail = data + seg2;
    }
    else
    {
        memcpy(tail, data_, count);
        tail += count;
    }

    return true;
}

template <class T>
bool CLoopBuffer<T>::PopFront(T* data_, int count)
{
    count = count*sizeof(T);
    int dist = (int)(tail + size - head);
    int used = dist >= size ? (dist - size) : dist;

    if (count > used)
        return false;

    if (head + count >= data + size)
    {
        int seg1 = (int)(data + size - head);
        int seg2 = count - seg1;
        memcpy(data_, head, seg1);
        memcpy((char*)data_+seg1, data, seg2);
        head = data + seg2;
    }
    else
    {
        memcpy(data_, head, count);
        head += count;
    }

    return true;
}

template <class T>
bool CLoopBuffer<T>::GetFront(T* data_, int count)
{
    count = count*sizeof(T);
    int dist = (int)(tail + size - head);
    int used = dist >= size ? (dist - size) : dist;

    if (count > used)
        return false;

    if (head + count >= data + size)
    {
        int seg1 = (int)(data + size - head);
        int seg2 = count - seg1;
        memcpy(data_, head, seg1);
        memcpy((char*)data_+seg1, data, seg2);
    }
    else
    {
        memcpy(data_, head, count);
    }

    return true;
}

template <class T>
void CLoopBuffer<T>::DiscardFront(int count)
{
    count = count*sizeof(T);
    int dist = (int)(tail + size - head);
    int used = dist >= size ? (dist - size) : dist;

    if (count > used)
        return;

    if (head + count >= data + size)
    {
        int seg1 = (int)(data + size - head);
        int seg2 = count - seg1;
        head = data + seg2;
    }
    else
    {
        head += count;
    }
}

template <class T>
T* CLoopBuffer<T>::GetData(void)
{
    return (T*)data;
}

template <class T>
int CLoopBuffer<T>::GetCount(void)
{
    return (int)size/sizeof(T);
}

template <class T>
int CLoopBuffer<T>::GetFreeCount(void)
{
    int dist = (int)(tail + size - head);
    int used = dist >= size ? (dist - size) : dist;

    return (size - used -1)/sizeof(T);
}